/*
 * miniFreeRTOS SOC System emulation.
 *
 * Copyright (c) 2024- yanl1229@163.com.
 * Written by yanl1229
 *
 * This code is licensed under the GPL.
 */

#include "irq.h"
#include "task.h"
#include "usart.h"

uint32_t interrupt_nest_count = 0xffffffffUL;

/**
 * 进入临界区
 */
void disable_interrupt(void)
{
    /* 屏蔽中断(高于系统调用的优先级中断才能被响应) */
    irq_mask();
    interrupt_nest_count++;
}

/**
 * 退出临界区
 */
void enable_interrupt(void)
{
    interrupt_nest_count--;

    /* 如果是嵌套多次调用，最后一次的退出临界区，才使能cpu中断 */
    if (interrupt_nest_count == 0)
    {
        /* 所有中断，都可以响应 */
        irq_unmask();
    }
}

/**
 * @brief  This function handles NMI exception.
 * @param  None
 * @retval None
 */
void NMI_Handler(void)
{
}

/**
 * @brief  This function handles Hard Fault exception.
 * @param  None
 * @retval None
 */
void HardFault_Handler(void)
{
    /* Go to infinite loop when Hard Fault exception occurs */
    while (1)
    {
    }
}

/**
 * @brief  This function handles Memory Manage exception.
 * @param  None
 * @retval None
 */
void MemManage_Handler(void)
{
    /* Go to infinite loop when Memory Manage exception occurs */
    while (1)
    {
    }
}

/**
 * @brief  This function handles Bus Fault exception.
 * @param  None
 * @retval None
 */
void BusFault_Handler(void)
{
    /* Go to infinite loop when Bus Fault exception occurs */
    while (1)
    {
    }
}

/**
 * @brief  This function handles Usage Fault exception.
 * @param  None
 * @retval None
 */
void UsageFault_Handler(void)
{
    /* Go to infinite loop when Usage Fault exception occurs */
    while (1)
    {
    }
}

/**
 * @brief  This function handles SVCall exception.
 * @param  None
 * @retval None
 */
void SVC_Handler(void)
{
    __asm volatile(
        "	mov	r3, %0		                    \n" /* 获取当前任务的地址 */
        "	ldr r1, [r3]					    \n" /* 获得当前任务内容 */
        "	ldr r0, [r1]					    \n" /* 获的当前任务stack的地址 */
        "	ldmia r0!, {r4-r11, r14}		    \n" /* 将当前任务stack上寄存器中的内容恢复到CPU寄存器(软件操作) */
        "	msr psp, r0						    \n" /* 将R0的值赋值给psp, 因为退出中断时，从psp自动恢复剩下的寄存器 */
        "	isb								    \n"
        "	mov r0, #0 						    \n"
        "	msr	basepri, r0					    \n" /* 将0赋值给中断屏蔽寄存器，为0时开启所有中断 */
        "	bx r14							    \n" /* 跳转到任务的任务函数中执行，也就是跳转pc指向任务函数地址去执行 */
        ::"r"(&current) : "memory");
}

/**
 * @brief  This function handles Debug Monitor exception.
 * @param  None
 * @retval None
 */
void DebugMon_Handler(void)
{
}

/**
 * @brief  This function handles PendSVC exception.
 * @param  None
 * @retval None
 */
void PendSV_Handler(void)
{
    __asm volatile(
        "	mrs r0, psp							\n" /* 发生异常时current任务的psp栈指针 */
        "	isb									\n"
        "										\n"
        "	mov	r3, %0			                \n" /* current地址保存在r3中 */
        "	ldr	r2, [r3]						\n" /* 当前正在执行的任务TCB保存在r2中 */
        "										\n"
        "	stmdb r0!, {r4-r11, r14}			\n" /* 将r4~r11 14寄存器中值，压入current的栈中 */
        "	str r0, [r2]						\n" /* 保存current任务的栈顶指针 */
        "										\n"
        "	stmdb sp!, {r0, r3}					\n" /* 入栈: 将R0，R3临时压入到hander模式的msp主栈 */
        "	mov r0, %1 							\n" /* r0 = configMAX_SYSCALL_INTERRUPT_PRIORITY */
        "	msr basepri, r0						\n" /* 将r0写入中断屏蔽寄存器，关闭5~15中断优先级的中断 */
        "	dsb									\n"
        "	isb									\n"
        "	bl switch_context				    \n" /* 查找下一个要运行的任务 */
        "	mov r0, #0							\n"
        "	msr basepri, r0						\n"
        "	ldmia sp!, {r0, r3}					\n" /* 出栈: 恢复之前在sp栈上保存的r0和r3 */
        "										\n"
        "	ldr r1, [r3]						\n" /* 取出r3地址中的值 */
        "	ldr r0, [r1]						\n" /* 获取r3地址中的内容，即下一个待运行任务的栈顶内容保存在r0中 */
        "										\n"
        "	ldmia r0!, {r4-r11, r14}			\n" /* 将r0地址中的值出栈，恢复到r4~r11,r14,即设置新任务的cpu寄存器的值 */
        "										\n"
        "	msr psp, r0							\n" /* 将r0的值赋值给psp, 设置新任务的进程栈指针位置 */
        "	isb									\n"
        "										\n"
        "	bx r14								\n" /* 异常返回，新任务开始运行 */
        "										\n" ::"r"(&current),
        "i"(CONFIG_MAX_SYSCALL_INTERRUPT_PRIORITY) : "memory");
}
/**
 * @brief  This function handles SysTick Handler.
 * @param  None
 * @retval None
 */
void SysTick_Handler(void)
{
    HAL_IncTick();
    if (get_sched_stat() == SCHED_RUNNIING)
        sched_tick();
}
